%% @author Bruno
%% @doc @todo Add description to Movimentador.


-module(movimentador).

%% ====================================================================
%% API functions
%% ====================================================================
-export([loop/0]).

loop()->
	controle_movimento ! {executor_livre, self()},
	receive
		{movimentar, Criatura}->
			moverCriatura(Criatura)
	end,
	loop().

%% ====================================================================
%% Internal functions
%% ====================================================================

moverCriatura(Criatura)->
	Tipo = criatura:getTipo(Criatura),
	if Tipo =:= presa ->
		   moverPresa(Criatura);
	   true ->
		   moverPredador(Criatura)
	end.
		   
moverPresa(Criatura)->
	Direcao = obterDirecaoPresa(Criatura),
	terreno ! {moverCriatura, criatura:id(Criatura), Direcao}.

moverPredador(Criatura)->
	Direcao = obterDirecaoPredador(Criatura),
	terreno ! {moverCriatura, criatura:id(Criatura), Direcao}.


obterDirecaoPredador(Criatura) ->
	DeltaCiclo = criatura:getCiclo(Criatura) - criatura:getCicloRefeicao(Criatura),
	IntervaloMorteFome =  criatura:getIntervaloMorteFome(),
	AptoParaProcriar = criatura:aptoParaProcriar(Criatura),
	
	if (DeltaCiclo =< (IntervaloMorteFome / 2)) and AptoParaProcriar ->
		   	Alvo = predador;
		DeltaCiclo >= (IntervaloMorteFome / 3) ->
		   	Alvo = presa;
		true ->
			Alvo = undefined
	end,
	getDirecao(Criatura, Alvo).

obterDirecaoPresa(Criatura) ->
	AptoParaProcriar = criatura:aptoParaProcriar(Criatura),
	DirecaoPredador = getDirecao(Criatura, predador, 10),
	
	if DirecaoPredador =/= undefined ->
		   	direcao:inverter(DirecaoPredador);
		AptoParaProcriar ->
		    getDirecao(Criatura, presa, 50);
	    true ->
			getDirecao(Criatura, undefined)
	end.

getDirecao(Criatura, Alvo) ->
	getDirecao(Criatura, Alvo, 1000).

getDirecao(Criatura, Alvo, Distancia) when Alvo =:= presa ->
	Posicoes = getListaPosicaoRelativaPresas(Criatura),
	Posicao = getPosicaoMaisProxima(Posicoes, Distancia),
	if Posicao =/= undefined ->
		   direcao:getDirecaoRelativa(Posicao);
	   true ->
		   getDirecao(Criatura, undefined, undefined)
	end;
getDirecao(Criatura, Alvo, Distancia) when Alvo =:= predador ->
	Posicoes = getListaPosicaoRelativaPredadores(Criatura),
	Posicao = getPosicaoMaisProxima(Posicoes, Distancia),
	if Posicao =/= undefined ->
		   direcao:getDirecaoRelativa(Posicao);
	   true ->
		   getDirecao(Criatura, undefined, undefined)
	end;
getDirecao(_, Alvo, _) when Alvo =:= undefined ->
	direcao:getDirecaoAleatoria().

getListaPosicaoRelativaPresas(Criatura) -> 
	terreno ! {obterPosicoesPresas, self(), criatura:id(Criatura)},
	receive
		{listaPosicoesPresas, {Xref, Yref}, Posicoes} ->
			calcularPosicoesRelativas(Xref, Yref, Posicoes, [])
	end.

getListaPosicaoRelativaPredadores(Criatura) -> 
	terreno ! {obterPosicoesPredadores, self(), criatura:id(Criatura)},
	receive
		{listaPosicoesPredadores, {Xref, Yref}, Posicoes} ->
			calcularPosicoesRelativas(Xref, Yref, Posicoes, [])
	end.

getPosicaoMaisProxima(Posicoes, Distancia) ->
	PosicoesNaArea = filtrarPosicoesDistantes(Posicoes, Distancia, []),
	if length(PosicoesNaArea) > 0 ->
		   [H|T] = PosicoesNaArea,
		   selecionarPosicaoMaisProxima(H, T);
	   true ->
		   undefined
	end.

selecionarPosicaoMaisProxima({X0,Y0}, [{X1, Y1}|T]) ->
	D0 = calcularDistanciaRelativa({X0,Y0}),
	D1 = calcularDistanciaRelativa({X1,Y1}),
	if D0 > D1 ->
		   selecionarPosicaoMaisProxima({X1,Y1}, T);
	   true ->
		   selecionarPosicaoMaisProxima({X0,Y0}, T)
	end;
selecionarPosicaoMaisProxima(MaisProxima, []) ->
	MaisProxima.

calcularDistanciaRelativa({X, Y}) ->
	math:sqrt(X * X + Y * Y).

calcularPosicoesRelativas(Xref, Yref, [{X, Y} | TCP], Posicoes) when Xref /= X, Yref /= Y ->
	calcularPosicoesRelativas(Xref, Yref, TCP, [{X - Xref, Y - Yref}] ++ Posicoes);
calcularPosicoesRelativas(Xref, Yref, [_|TCP], Posicoes) ->
	calcularPosicoesRelativas(Xref, Yref, TCP, Posicoes);
calcularPosicoesRelativas(_, _, [], Posicoes) ->
	Posicoes.

filtrarPosicoesDistantes([Posicao | T], Distancia, PosicoesNaArea) ->
	D = calcularDistanciaRelativa(Posicao),
	if D =< Distancia ->
		   filtrarPosicoesDistantes(T, Distancia, [Posicao | PosicoesNaArea]);
		true ->
	       filtrarPosicoesDistantes(T, Distancia, PosicoesNaArea)
	end;
filtrarPosicoesDistantes([], _, PosicoesNaArea) ->
	PosicoesNaArea.
